﻿using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Guids;
using Volo.Abp.Timing;
using Xms.Abp.Threading;

namespace Xms.Abp.BackgroundJob;

[Dependency(ReplaceServices = true)]
public class DefaultBackgroundScheduleManager : IBackgroundScheduleManager, ITransientDependency
{
    protected IBackgroundScheduleStore Store { get; }
    protected IClock Clock { get; }
    protected IGuidGenerator GuidGenerator { get; }
    protected IBackgroundJobSerializer JobSerializer { get; }

    private const string PREFIX = "#Dynamic#";

    public DefaultBackgroundScheduleManager(IBackgroundScheduleStore store, IClock clock, IGuidGenerator guidGenerator, IBackgroundJobSerializer jobSerializer)
    {
        Store = store;
        Clock = clock;
        GuidGenerator = guidGenerator;
        JobSerializer = jobSerializer;
    }

    public async Task DeleteAsync(Guid busId, string jobName, CancellationToken stoppingToken = default)
    {
        await Store.DeleteAsync(GenerateName(busId, jobName), stoppingToken);
    }

    public async Task DeleteAsync(Guid id, CancellationToken stoppingToken = default)
    {
        await Store.DeleteAsync(id.ToString(), stoppingToken);
    }

    public async Task<Guid> EnqueueAsync(
        Guid busId,
        string jobName,
        string displayName,
        object args,
        string cron,
        bool isEnabled,
        Guid? tenantId,
        BackgroundJobPriority priority = BackgroundJobPriority.Normal,
        int? timeout = null,
        short? maxTryCount = null,
        IEnumerable<int> retryIntervals = null,
        CancellationToken stoppingToken = default)
    {
        var id = GuidGenerator.Create();
        var nextExecuteTime = Cron.Parse(cron).GetNextOccurrence(DateTime.Now) ?? DateTime.MaxValue;
        var info = new BackgroundScheduleInfo
        {
            Id = id,
            Name = GenerateName(busId, jobName),
            DisplayName = displayName,
            Cron = cron,
            CreationTime = DateTime.Now,
            ExecuteCount = 0,
            IsEnabled = isEnabled,
            LastExecuteTime = DateTime.MaxValue,
            ModificationTime = DateTime.Now,
            MultiTenancySides = tenantId.HasValue ? Volo.Abp.MultiTenancy.MultiTenancySides.Tenant : Volo.Abp.MultiTenancy.MultiTenancySides.Host,
            NextExecuteTime = nextExecuteTime,
        };
        info.SetProperty(nameof(jobName), jobName, false);
        info.SetProperty(nameof(args), args is string argsStr ? argsStr : JobSerializer.Serialize(args), false);
        info.SetProperty(nameof(tenantId), tenantId, false);
        info.SetProperty(nameof(priority), priority, false);
        if (timeout.HasValue)
            info.SetProperty(nameof(timeout), timeout, false);
        if (maxTryCount.HasValue)
            info.SetProperty(nameof(maxTryCount), maxTryCount, false);
        if (retryIntervals != null)
            info.SetProperty(nameof(retryIntervals), retryIntervals.JoinAsString(","), false);
        await Store.InsertAsync(info, stoppingToken);
        return id;
    }

    public async Task UpdateAsync(Guid id, object args, string cron, bool isEnabled, CancellationToken stoppingToken = default)
    {
        var nextExecuteTime = Cron.Parse(cron).GetNextOccurrence(DateTime.Now) ?? DateTime.MaxValue;
        var info = await Store.FindAsync(id, stoppingToken);
        info.Cron = cron;
        info.IsEnabled = isEnabled;
        info.ModificationTime = DateTime.Now;
        info.NextExecuteTime = nextExecuteTime;
        info.SetProperty(nameof(args), args is string argsStr ? argsStr : JobSerializer.Serialize(args), false);
        await Store.UpdateAsync(info, stoppingToken);
    }

    private string GenerateName(Guid busId, string jobName)
    {
        return PREFIX + jobName + "#" + busId;
    }

    public bool IsAvailable()
    {
        return true;
    }
}
